package com.mlming.simpledb.backend.dm.dataItem;

import com.google.common.primitives.Bytes;
import com.mlming.simpledb.backend.common.SubArray;
import com.mlming.simpledb.backend.dm.DataManagerImpl;
import com.mlming.simpledb.backend.dm.page.Page;
import com.mlming.simpledb.backend.utils.Parser;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * dataItem 结构如下：
 * [ValidFlag] [DataSize] [Data]
 * ValidFlag 1字节，0为合法，1为非法
 * DataSize  2字节，标识Data的长度
 */
public class DataItemImpl implements DataItem {

    static final int OF_VALID = 0; // [ValidFlag]偏移量
    static final int OF_SIZE = 1; // [DataSize]偏移量
    static final int OF_DATA = 3; // [Data]偏移量

    static final byte VALID_FLAG_VALID = 0; // 0合法
    static final byte VALID_FLAG_NOT_VALID = 1; // 1非法

    private SubArray raw; // DataItem实际的数据: [ValidFlag] [DataSize] [Data]

    private byte[] oldRaw; // 旧值
    private Lock rLock; // 读锁
    private Lock wLock;// 写锁

    private DataManagerImpl dm; // DataItem的缓存机制

    private Page pg; // 该数据([ValidFlag] [DataSize] [Data])存在的页
    private int pgno; // 页号
    private short offset; // 在页中的偏移量

    public DataItemImpl(SubArray raw, byte[] oldRaw, Page pg, int pgno, short offset, DataManagerImpl dm) {
        this.raw = raw;
        this.oldRaw = oldRaw;
        ReadWriteLock lock = new ReentrantReadWriteLock();
        rLock = lock.readLock();
        wLock = lock.writeLock();
        this.dm = dm;
        this.pgno = pgno;
        this.offset = offset;
        this.pg = pg;
    }

    @Override
    // 返回整个DataItem实际数据
    public SubArray getRaw() {
        return this.raw;
    }

    @Override
    // 返回[Data]
    public SubArray data() {
        return new SubArray(this.raw.raw,this.raw.start + OF_DATA,this.raw.end);
    }

    @Override
    // 获得DataItem的唯一标识: pgno + offset
    public long getUid() {
        // 拼接pgno和offset
        return Parser.parseLong(Bytes.concat(Parser.int2Byte(this.pgno),Parser.short2Byte(this.offset)));
    }

    @Override
    // 在修改DataItem之前必须要调用此方法, 用来保存当前值作为旧值存在oldRaw里, 以实现之后的撤销/回滚
    public void before() {
        wLock.lock(); // 因为要修改了, 所以加写锁
        pg.setDirty(true); // 因为要修改了, 所以把该页变为脏页
        // 把当前实际数据, 即 raw 存到oldRaw里面去
        System.arraycopy(raw.raw, raw.start, oldRaw, 0, oldRaw.length);
    }

    @Override
    // 在撤销修改时, 可以调用该方法, 其实就是把before里保存到oldRaw里面的值又赋值到raw里面去
    public void unBefore() {
        // 把before里保存到oldRaw里面的值又赋值到raw里面去
        System.arraycopy(oldRaw, 0, raw.raw, raw.start, oldRaw.length);
        // 因为在撤销修改之前, 必然是发送了修改, 既然发送了修改一定调用了before方法, 既然调用了before方法就一定加了锁
        // 所以要解开
        wLock.unlock();
    }

    @Override
    // 在修改DataItem**之后**必须调用该方法, 用来记录日志
    // 因为我们说DM的功能其中之一就是记录日志, 所以说这里肯定是调用DM的方法间接来调用Logger方法来记录日志
    public void after(long xid) {
        dm.logDataItem(xid,this);
        // 因为在完成修改之前, 必然是发送了修改, 既然发送了修改一定调用了before方法, 既然调用了before方法就一定加了锁
        // 所以要解开
        wLock.unlock();
    }

    @Override
    public void release() {
        dm.release(this);
    }

    @Override
    public void lock() {
        wLock.lock();
    }

    @Override
    public void unlock() {
        wLock.unlock();
    }

    @Override
    public void rLock() {
        rLock.lock();
    }

    @Override
    public void rUnLock() {
        rLock.unlock();
    }

    @Override
    public Page page() {
        return this.page();
    }

    @Override
    public int getPgno() {
        return this.pgno;
    }

    @Override
    public short getOffset() {
        return this.offset;
    }

    @Override
    public byte[] getOldRaw() {
        return this.oldRaw;
    }

    // 判断该数据是否合法
    public boolean isValid() {
        return raw.raw[raw.start+OF_VALID] == (byte)VALID_FLAG_VALID;
    }
}
